package cooperate

import (
	"errors"
	"fmt"
	"math/rand"
	"sync"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/common/feature/orbit/chess_protocol"
	"gddgame.cc/galaxy/common/namespaces/cooperate/structure"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/serialize"
)

type MapNodeDelegate interface {
	GetMapNodes(mapNoId string) []string
	GetNodeMaps(node string) []string
}

type MapNodeService interface {
	ID() string
	IsMaster(mapNoId string) bool
	OnStart(mapNoId string)
	OnStop(mapNoId string)
	OnNotice(oldSocket *nebula.Socket, notice *Notice)
	OnUpdateNode(mapNoId string, item *NoticeContent, deleteStatus bool, d PropsInstance)
	OnNoticeAccount(oldSocket *nebula.Socket, newSocket *nebula.Socket, item *NoticeContent, deleteStatus bool, d PropsInstance, content *chess_protocol.UpdatePublicData)
	OnNoticeManager(socket *nebula.Socket, mapNoId string, item *NoticeContent, deleteStatus bool, d PropsInstance, content *chess_protocol.UpdatePublicData)
	OnReleaseManager(socket *nebula.Socket, mapNoId string)
}

type MapNodeInstance interface {
	GetMapNoId() string
	GetMapInstance() *MapInstance

	GetClient() *cluster.Client
}

type MapSignal interface {
	MapDelegate
	Sync(event interface{}, params ...interface{}) interface{}
	Async(event interface{}, f func(r interface{}), params ...interface{})
}

type mapSignal struct {
	mapNoId string
	share   *Share
}

const (
	queryEvent int = 255
	listDepend int = 256
	getData        = 257
	listData       = 258
)

func (signal *mapSignal) OnQuery(accountId int, identity int, event byte, params []interface{}) interface{} {
	result := signal.share.sync(signal.mapNoId, queryEvent, []interface{}{accountId, identity, event, params})
	signal.Release()
	return result
}

func (signal *mapSignal) Sync(event interface{}, params ...interface{}) interface{} {
	result := signal.share.sync(signal.mapNoId, event, params)
	signal.Release()
	return result
}

func (signal *mapSignal) Async(event interface{}, f func(r interface{}), params ...interface{}) {
	signal.share.async(signal.mapNoId, event, f, params)
	signal.Release()
}

func (signal *mapSignal) Release() {
	signal.share.mapPool.Put(signal)
}

type Share struct {
	agent binocle.App

	logic      *LogicExecute
	mapManager *MapManager

	MapNodeService  MapNodeService
	MapNodeDelegate MapNodeDelegate

	InstanceMap sync.Map

	Listener      map[int]*nebula.Socket
	ListenerMutex sync.RWMutex

	ManagerListener      map[string]*nebula.Socket
	ManagerListenerMutex sync.RWMutex

	event   utils.Event
	mapPool sync.Pool
}

func (balance *Share) init() {
	// 在同一频道进行转发：转发指定对应node
	balance.Listener = map[int]*nebula.Socket{}
	balance.ListenerMutex = sync.RWMutex{}

	balance.ManagerListener = map[string]*nebula.Socket{}
	balance.ManagerListenerMutex = sync.RWMutex{}

	balance.event = utils.NewEvent()
	balance.mapPool = sync.Pool{New: func() interface{} {
		return &mapSignal{share: balance}
	}}

	balance.agent.MountClient(func(client *cluster.Client) {
		client.On("cooperate/start", func(message asteroid.Message) {
			bag := serialize.GetBag()
			bag.SetList(message.List())
			mapNoId := bag.ReadString(0)
			node := bag.ReadString(1)
			bag.Release()
			balance.startMap(mapNoId, node)
		})
		client.On("cooperate/stop", func(message asteroid.Message) {
			bag := serialize.GetBag()
			bag.SetList(message.List())
			mapNoId := bag.ReadString(0)
			node := bag.ReadString(1)
			bag.Release()
			balance.stopMap(mapNoId, func() {
				message.Response(true)
			}, node)
		})
		client.On("cooperate/releaseManager", func(message asteroid.Message) {

		})
		client.On("cooperate/signal", func(message asteroid.Message) {
			bag := serialize.GetBag()
			bag.SetList(message.List())
			mapNoId := bag.ReadString(0)
			event := bag.ReadInterface(1)
			params := bag.ReadList(2)
			bag.Release()
			mapInstance := balance.mapManager.GetMap(mapNoId)
			v, err := balance.execEvent(mapInstance, event, params)
			if err != nil {
				message.Response(nil)
			} else {
				message.Response(v)
			}
		})
	})

	balance.OnSignal(queryEvent, func(mapInstance MapProxy, accountId int, identity int, event byte, params []interface{}) interface{} {
		return mapInstance.OnQuery(accountId, identity, event, params)
	})
	balance.OnSignal(listDepend, func(mapInstance MapProxy, dataType structure.PublicDataType, id interface{}, key string) []PropsInstance {
		return balance.logic.ListDepend(mapInstance, dataType, id, key)
	})
	balance.OnSignal(getData, func(mapInstance MapProxy, dataType structure.PublicDataType, id interface{}) PropsInstance {
		return balance.logic.GetDetailCache(mapInstance, structure.NewBaseData(dataType, id))
	})
	balance.OnSignal(listData, func(mapInstance MapProxy, dataType structure.PublicDataType, ids []interface{}) []PropsInstance {
		return balance.logic.ListData(mapInstance, dataType, ids)
	})
}

func (balance *Share) execEvent(mapInstance MapProxy, event interface{}, params []interface{}) (interface{}, error) {
	vs, err := balance.event.FireInterface(event, append([]interface{}{mapInstance}, params...))
	if err != nil {
		return nil, err
	} else if len(vs) > 0 {
		return vs[0], err
	}
	return nil, nil
}

func (balance *Share) bindDelegate(delegate MapNodeDelegate) {
	balance.MapNodeDelegate = delegate
}

func (balance *Share) bindService(service MapNodeService) {
	balance.MapNodeService = service
}

func (balance *Share) GetMapInstance(mapNoId string) MapNodeInstance {
	k, ok := balance.InstanceMap.Load(mapNoId)
	if !ok {
		return nil
	}
	if k == nil {
		return nil
	}
	return k.(MapNodeInstance)
}

func (balance *Share) OnSignal(event interface{}, handler interface{}) {
	balance.event.On(event, handler)
}

func (balance *Share) IsLocal(mapNoId string) *MapInstance {
	return balance.mapManager.GetMap(mapNoId)
}

func (balance *Share) RegisterAccount(accountId int, socket *nebula.Socket) {
	balance.ListenerMutex.Lock()
	balance.Listener[accountId] = socket
	balance.ListenerMutex.Unlock()
}

func (balance *Share) UnRegisterAccount(accountId int) {
	balance.ListenerMutex.Lock()
	delete(balance.Listener, accountId)
	balance.ListenerMutex.Unlock()
}

// todo 记录通知到mapnoid对应服务
func (balance *Share) RegisterManager(mapNoId string, socket *nebula.Socket) {
	balance.ManagerListenerMutex.Lock()
	balance.ManagerListener[mapNoId] = socket
	balance.ManagerListenerMutex.Unlock()
}

func (balance *Share) UnRegisterManager(mapNoId string) {
	balance.ManagerListenerMutex.Lock()
	delete(balance.ManagerListener, mapNoId)
	balance.ManagerListenerMutex.Unlock()
}

func (balance *Share) InitClient(instance MapNodeInstance, client *cluster.Client) {
	mapInstance := instance.GetMapInstance()
	mapNoId := instance.GetMapNoId()
	balance.InstanceMap.Store(mapNoId, instance)
	client.On("start", func(message asteroid.Message) {
		fmt.Println("[ Cooperate ] Map start")
		balance.MapNodeService.OnStart(instance.GetMapNoId())
	})
	client.On("stop", func(message asteroid.Message) {
		fmt.Println("[ Cooperate ] Map stop")
		balance.MapNodeService.OnStop(instance.GetMapNoId())
		message.Response(true)
	})
	client.On("queue", func(message asteroid.Message) {
		var queue Queue
		slot := serialize.GetSlot()
		message.UnSerialize(slot)
		if err := slot.Set(&queue); err != nil {
			fmt.Println("queue", err)
			slot.Release()
			message.Response(false)
			return
		}
		balance.logic.PopQueue(queue)
		slot.Release()
	})
	client.On("notice", func(message asteroid.Message) {
		var notice Notice
		slot := serialize.GetSlot()
		message.UnSerialize(slot)
		if err := slot.Set(&notice); err != nil {
			fmt.Println("notice", err)
			slot.Release()
			message.Response(false)
			return
		}
		balance.ListenerMutex.RLock()
		socket, ok := balance.Listener[notice.AccountId]
		balance.ListenerMutex.RUnlock()
		if ok {
			balance.MapNodeService.OnNotice(socket, &notice)
		}
		slot.Release()
	})
	client.On("releaseManager", func(message asteroid.Message) {
		balance.ManagerListenerMutex.RLock()
		socket, ok := balance.ManagerListener[mapNoId]
		balance.ManagerListenerMutex.RUnlock()
		if ok {
			balance.MapNodeService.OnReleaseManager(socket, mapNoId)
			balance.UnRegisterManager(mapNoId)
		}
		message.Response(true)
	})
	client.On("update", func(message asteroid.Message) {
		var notice UpdateNotice
		slot := serialize.GetSlot()
		message.UnSerialize(slot)
		if err := slot.Set(&notice); err != nil {
			fmt.Println("update", err)
			slot.Release()
			message.Response(false)
			return
		}
		//fmt.Println("notice", len(notice.List))
		for _, item := range notice.List {
			//fmt.Println("item", item)

			var d PropsInstance
			var content *chess_protocol.UpdatePublicData
			var deleteStatus bool

			isNode := balance.logic.IsNodeType(item.Data.DataType)
			//if item.Content.Mode == 0 {
			//	fmt.Println("update", item.Data, item.Content.Props, item.Content.Dependents, item.Content.Event)
			//}
			d, content, deleteStatus = balance.logic.UpdateDetailCache(client.CurrentNode() == notice.Node, mapInstance, &item.Data, &item.Content)

			//if item.Content.UpdatePublicData != nil {
			//	fmt.Println("update", d.BaseData(), item.Content.Event, item.Content.Extend, item.Content.Reset, item.Content.Props, item.Content.UpdatePublicData, content)
			//}
			//fmt.Println("update", d.Data(), oldOk, newOk, manager, oldManager, item.OldAccountId, item.NewAccountId)
			// 更新寻路状态
			if isNode {
				balance.MapNodeService.OnUpdateNode(instance.GetMapNoId(), &item, deleteStatus, d)
			}
			if content == nil && !deleteStatus {
				continue
			}
			var oldSocket *nebula.Socket
			var newSocket *nebula.Socket
			if item.OldAccountId != 0 {
				balance.ListenerMutex.RLock()
				oldSocket, _ = balance.Listener[item.OldAccountId]
				balance.ListenerMutex.RUnlock()
			}
			if item.OldAccountId != item.NewAccountId && item.NewAccountId != 0 {
				balance.ListenerMutex.RLock()
				newSocket, _ = balance.Listener[item.NewAccountId]
				balance.ListenerMutex.RUnlock()
				//fmt.Println("notice", item.NewAccountId, newOk)
			} else {
				newSocket = nil
			}
			balance.MapNodeService.OnNoticeAccount(oldSocket, newSocket, &item, deleteStatus, d, content)
			if d != nil {
				manager, oldManager := d.Manager(false)
				if manager != nil || oldManager != nil {
					// todo 判断所在服务器，转发
					mapNoId := d.GetMapNoId()
					balance.ManagerListenerMutex.RLock()
					managerSocket, managerOk := balance.ManagerListener[mapNoId]
					balance.ManagerListenerMutex.RUnlock()
					if managerOk {
						balance.MapNodeService.OnNoticeManager(managerSocket, mapNoId, &item, deleteStatus, d, content)
					}
				}
			}

		}
		slot.Release()
		message.Response(true)
	})
}

func (balance *Share) startMap(mapNoId string, node string) {
	fmt.Println("startmap", mapNoId, node)
	client := balance.agent.Balance().GenerateClient(balance.MapNodeService.ID(), mapNoId)
	//fmt.Println("startmap", 2)
	pipe := client.Emit()
	//fmt.Println("startmap", 3)
	pipe.Node(node)
	pipe.Send("start")
}

func (balance *Share) StartMap(mapNoId string, node string) {
	//fmt.Println("share", mapNoId, node, balance.agent.Node())
	if node == balance.agent.Node() {
		balance.startMap(mapNoId, node)
		return
	}
	nodes := balance.MapNodeDelegate.GetMapNodes(mapNoId)
	for _, node := range nodes {
		if balance.agent.Node() == node {
			balance.startMap(mapNoId, node)
			return
		}
	}
	balance.agent.Client().Emit().Node(node).List(mapNoId, node).Send("cooperate/start")
}

func (balance *Share) stopMap(mapNoId string, fn func(), node string) {
	client := balance.agent.Balance().GenerateClient(balance.MapNodeService.ID(), mapNoId)
	pipe := client.Emit()
	pipe.Node(node)
	pipe.Callback(func(success bool, data map[string]interface{}) {
		if fn != nil {
			fn()
		}
	}).Send("stop")
}

func (balance *Share) StopMap(mapNoId string, fn func(), node string) {
	if node == balance.agent.Node() {
		balance.stopMap(mapNoId, fn, node)
		return
	}
	nodes := balance.MapNodeDelegate.GetMapNodes(mapNoId)
	for _, node := range nodes {
		if balance.agent.Node() == node {
			balance.stopMap(mapNoId, fn, node)
			return
		}
	}
	balance.agent.Client().Emit().Node(node).List(mapNoId, node).Send("cooperate/stop")
}

func (balance *Share) ReleaseManager(node string, id string, mapNoId string) {
	if balance.agent.Node() == node {
		balance.ManagerListenerMutex.RLock()
		socket, ok := balance.ManagerListener[mapNoId]
		balance.ManagerListenerMutex.RUnlock()
		if ok {
			balance.MapNodeService.OnReleaseManager(socket, mapNoId)
			balance.UnRegisterManager(mapNoId)
		}
	} else {
		td, _ := balance.InstanceMap.Load(mapNoId)
		if td == nil {
			return
		}
		instance := td.(MapNodeInstance)
		if instance != nil {
			//fmt.Println("update", instance, notice.MapNoId, notice.Node)
			instance.GetClient().Emit().Node(node).Data(mapNoId).Send("releaseManager")
		}
	}

}

// todo 自动平衡：根据mapNoId发送到目标节点
// 随着连接数而动态调整需要的服务数
// 保证每台服务器的服务数相同
// 地图只有关闭才能降低服务数
func (balance *Share) OnUpdate(notice *UpdateNotice) {
	td, _ := balance.InstanceMap.Load(notice.MapNoId)
	if td == nil {
		fmt.Println("update notice", notice)
		return
	}
	slot := serialize.GetSlot()
	_ = slot.Set(notice)
	instance := td.(MapNodeInstance)
	if instance != nil {
		notice.Node = balance.agent.Node()
		//fmt.Println("update", instance, notice.MapNoId, notice.Node)
		instance.GetClient().Emit().Nodes(instance.GetClient().AllNode()).Data(slot).Send("update")
	}
	slot.Release()
}

func (balance *Share) OnNotice(notice *Notice) {
	balance.ListenerMutex.RLock()
	socket, ok := balance.Listener[notice.AccountId]
	balance.ListenerMutex.RUnlock()
	//fmt.Println("notice", notice)
	if ok {
		balance.MapNodeService.OnNotice(socket, notice)

	} else {
		td, _ := balance.InstanceMap.Load(notice.MapNoId)
		if td == nil {
			return
		}
		slot := serialize.GetSlot()
		_ = slot.Set(notice)
		instance := td.(MapNodeInstance)
		if instance != nil {
			instance.GetClient().Emit().Nodes(instance.GetClient().OtherNodes()).Data(slot).Send("notice")
		}
		slot.Release()
	}
}

// 将操作发送到
func (balance *Share) OnQueue(queue *Queue) {
	td, _ := balance.InstanceMap.Load(queue.Content.MapNoId)
	if td == nil {
		fmt.Println("queue notice", queue)
		return
	}
	slot := serialize.GetSlot()
	_ = slot.Set(queue)
	instance := td.(MapNodeInstance)
	if instance != nil {
		//fmt.Println("send", queue.Content)
		if queue.Content.Key == "" {
			queue.Content.Key = utils.ToString(queue.Content.Target.GetItem())
		}
		instance.GetClient().Emit().Node(instance.GetClient().Alloc(queue.Content.Key)).Data(slot).Send("queue")
	}
	slot.Release()
}

func (balance *Share) Signal(mapNoId string) MapSignal {
	signal := balance.mapPool.Get().(*mapSignal)
	signal.mapNoId = mapNoId
	return signal
}

func (share *Share) async(mapNoId string, event interface{}, result func(r interface{}), params []interface{}) {
	mapInstance := share.IsLocal(mapNoId)
	//fmt.Println("share", mapNoId, mapInstance)
	if mapInstance != nil {
		v, err := share.execEvent(mapInstance, event, params)
		if err != nil {
			result(err)
			return
		}
		result(v)
		return
	}
	nodes := share.MapNodeDelegate.GetMapNodes(mapNoId)
	share.agent.Client().Emit().Node(nodes[rand.Int31n(int32(len(nodes)))]).List(mapNoId, event, params).CallbackTo(func(success bool, data interface{}) {
		if success {
			result(data)
			return
		}
		result(errors.New("执行失败"))
	}).Send("cooperate/signal")
}

func (share *Share) sync(mapNoId string, event interface{}, params []interface{}) interface{} {
	var result interface{}
	c := make(chan struct{}, 1)
	share.async(mapNoId, event, func(r interface{}) {
		result = r
		c <- struct{}{}
	}, params)
	<-c
	//fmt.Println("sync", result)
	return result
}

func (share *Share) ListDepend(mapNoId string, dataType structure.PublicDataType, id interface{}, key string) []PropsInstance {
	result := share.sync(mapNoId, listDepend, []interface{}{dataType, id, key})
	switch r := result.(type) {
	case []PropsInstance:
		return r
	}
	return nil
}

func (share *Share) GetData(mapNoId string, dataType structure.PublicDataType, id interface{}) PropsInstance {
	result := share.sync(mapNoId, getData, []interface{}{dataType, id})
	switch r := result.(type) {
	case PropsInstance:
		return r
	}
	return nil
}

func (share *Share) TmpData(dataType structure.PublicDataType, id interface{}) PropsInstance {
	d := share.mapManager.DefaultInstance()
	return share.logic.GetDetailTmp(d, structure.NewBaseData(dataType, id), nil)
}

func (share *Share) ListData(mapNoId string, dataType structure.PublicDataType, ids []interface{}) []PropsInstance {
	result := share.sync(mapNoId, listData, []interface{}{dataType, ids})
	switch r := result.(type) {
	case []PropsInstance:
		return r
	}
	return nil
}

func (share *Share) PushNotice(mapNoId string, accountId int, event byte, content []byte) {
	// 会转发到share，无需考虑
	share.logic.PushNotice(mapNoId, accountId, event, content)
}
